package com.xhy.documents_collection.restrict.impl;

import com.xhy.documents_collection.cache.UserCache;
import com.xhy.documents_collection.entity.VO.DarkRoomUser;
import com.xhy.documents_collection.holder.UserHolder;
import com.xhy.documents_collection.restrict.Restrict;

import java.util.*;
import java.util.concurrent.*;

/**
 * Author: Xhy
 * CreateTime: 2023-03-08 08:51
 * 计数器限流
 */
public class CounterRestrict implements Restrict {

    private Map<String, Data> map = new HashMap<>();
    private BlockingQueue<DataDelay> delayQueue = new DelayQueue<>();

    public static Set<DarkRoomUser> darkRoomUsers = new HashSet<>();

    private boolean state = false;

    @Override
    public void set(String key, Integer value, long time) {
        map.put(key, new Data(value,time));
        delayQueue.add(new DataDelay(key, time));
    }

    @Override
    public Integer get(String key) {
        return map.get(key).value;
    }

    @Override
    public void remove(String key) {
        map.remove(key);
        delayQueue.remove(new DataDelay(key));
    }

    /**
     * 自增,不存在则添加
     * @param key
     */
    @Override
    public void incr(String key,long time) {
        if (map.containsKey(key)){
            map.get(key).incr();
        }else {
            set(key,1,time);
        }
    }

    /**
     * 达到限制让用户time期间内不允许访问
     * 如果用户执意访问则延续time期间
     * @param key
     * @param limit
     * @return
     */
    @Override
    public boolean check(String key, Integer limit) {
        if (!map.containsKey(key)){
            return false;
        }
        Data data = map.get(key);
        boolean res =  data.value >= limit;
        if (res){
            removeDelayKey(key);
            addDelay(key,data.time);
            addDarkRoom(key);
        }
        return res;
    }

    private void addDarkRoom(String key){
        // 关进小黑屋
        DarkRoomUser user = new DarkRoomUser();
        user.setBanTime(new Date());
        Integer userId = UserHolder.get();
        user.setId(userId);
        user.setName(UserCache.get(userId));
        user.setKey(key);
        darkRoomUsers.add(user);
    }

    public void removeDarkRoomInUser(String key){
        Iterator<DarkRoomUser> iterator = darkRoomUsers.iterator();
        while (iterator.hasNext()) {
            DarkRoomUser next = iterator.next();
            if (next.getKey().equals(key)){
                iterator.remove();
                remove(key);
            }
        }
    }

    public static Set<DarkRoomUser> listUsers(){
        return darkRoomUsers;
    }

    private void removeDelayKey(String key){
        delayQueue.remove(new DataDelay(key));
    }

    private void addDelay(String key,long time){
        delayQueue.add(new DataDelay(key,time));
    }


    private class Data{
        Integer value;
        Long time;

        public Data(Integer value, long time) {

            this.value = value;
            this.time = time;
        }
        public void incr(){
            this.value+=1;
        }
        public Data(){

        }
    }

    private class DataDelay implements Delayed {

        String key;
        long expire;

        public DataDelay() {
        }

        public DataDelay(String key, long time) {
            // 第一次添加的时候init需要初始化
            init();
            this.key = key;
            this.expire = new Date().getTime() + (time * 1000);
        }

        public DataDelay(String key) {
            this.key = key;

        }

        private void init() {
            if (!state) {
                state = true;
                new Thread(() -> {
                    while (true) {
                        try {
                            DataDelay take = delayQueue.take();
                            map.remove(take.key);
                            removeDarkRoomInUser(take.key);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }, "CounterRestrict").start();
            }
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(this.expire - System.currentTimeMillis(), TimeUnit.MILLISECONDS);

        }

        @Override
        public int compareTo(Delayed o) {
            long f = this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS);
            return (int) f;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            DataDelay dataDelay = (DataDelay) o;
            return Objects.equals(key, dataDelay.key);
        }

        @Override
        public int hashCode() {
            return Objects.hash(key);
        }
    }


}
